package org.openstreetmap.josm.plugins.cadastre_es;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import java.util.Vector;

import javax.imageio.ImageIO;
import org.openstreetmap.josm.plugins.cadastre_es.exceptions.CriticalException;

public class Tile {
	
	private BufferedImage tile;
	private boolean saved = false;
	private boolean loaded = false;
	private int width;
	private int height;
	private int xTile;
	private int yTile;
	private int tries = 0;
	private int thumbnailTries = 0;
	private Bound<Double> latLonBound = new Bound<Double>();
	private Bound<Integer> bound = new Bound<Integer>();
	private boolean empty = false;
	public static int downloads = 0;
	public static int skipped = 0;
	public static final String WMS_URL = "http://ovc.catastro.meh.es/Cartografia/WMS/ServidorWMS.aspx?SERVICE=WMS&SRS=EPSG:4326&REQUEST=GETMAP&bbox=%.16f,%.16f,%.16f,%.16f&width=%d&height=%d&format=PNG&transparent=No&layers=constru";
	public static final int MAX_SIZE = 3900;
	private static final int MAX_TRIES = 3;
	private static final int SLEEP = 4000;
	private static final int WATER_MARK_HEIGHT = 12;
	private static final int THUMBNAIL_DIVISION = 18;
    private static Vector<Tile> loadedTiles = new Vector<Tile>();
    private static Vector<Tile> savedTiles = new Vector<Tile>();
	
	public Tile(int xTile, int yTile) {
		//System.out.println("init tile: "+ xTile+","+yTile);
		this.xTile = xTile;
		this.yTile = yTile;
				
		bound.left = MAX_SIZE * xTile;
		bound.right = MAX_SIZE * (xTile + 1);
		bound.top = MAX_SIZE * yTile;
		bound.bottom = MAX_SIZE * (yTile + 1);
		
		int lastX = CadastreImage.getWidth() - 1;
		int lastY = CadastreImage.getHeight() - 1;
		if (bound.right > lastX) {
			bound.right = lastX;
			width = CadastreImage.getWidth() - xTile * MAX_SIZE;
		} else
			width = MAX_SIZE;
		if (bound.bottom > lastY) {
			bound.bottom = lastY;
			height = CadastreImage.getHeight() - yTile * MAX_SIZE;
		} else
			height = MAX_SIZE;
		
		Bound<Double> gLatLonBound = CadastreImage.getLatLonBound();
		latLonBound.top = gLatLonBound.top - bound.top * CadastreImage.DEGREES_EACH_PIXEL;
		latLonBound.bottom = gLatLonBound.top - bound.bottom * CadastreImage.DEGREES_EACH_PIXEL;
		latLonBound.left = bound.left * CadastreImage.DEGREES_EACH_PIXEL + gLatLonBound.left;
		latLonBound.right = bound.right * CadastreImage.DEGREES_EACH_PIXEL + gLatLonBound.left;
		//stem.out.println("Tile: "+xTile+","+yTile+"; "+latLonBound.left+","+latLonBound.right+","+latLonBound.bottom+","+latLonBound.top+"; "+width+","+height);
	}

	public void download() throws CriticalException {
		if (!loaded) {
			if (emptyTile()) {
				empty = true;
				loaded = true;
				skipped++;
				return;
			}
			String url = String.format(new Locale("en", "UK"), WMS_URL, latLonBound.left,
					latLonBound.bottom - (WATER_MARK_HEIGHT * CadastreImage.DEGREES_EACH_PIXEL), latLonBound.right, latLonBound.top,
					width, height + WATER_MARK_HEIGHT);
			try {
				//System.out.print("download("+xTile+","+yTile+")... ");
				tile = ImageIO.read(new URL(url));
				closeTile();
				loaded = true;
				loadedTiles.add(this);
				downloads++;
				//System.out.println("done, savedTiles: "+savedTiles.size()+", loadedTiles: "+loadedTiles.size()+"... ");
			} catch (MalformedURLException e) {
				throw new CriticalException(e.getMessage());
			} catch (Exception e) {
				System.out.println("Error downloading image (" + tries + "/" + MAX_TRIES + ")");
				if (++tries < MAX_TRIES) {
					try {
						Thread.sleep(SLEEP);
					} catch (InterruptedException e1) {
						throw new CriticalException(e.getMessage());
					}
					download();
					return;
				}
				else
					throw new CriticalException(e.getMessage());
			}
		}
	}
	
	public boolean emptyTile() throws CriticalException {
		BufferedImage thumbnailTile;
		String url = String.format(new Locale("en", "UK"), WMS_URL, latLonBound.left,
				latLonBound.bottom - (WATER_MARK_HEIGHT * CadastreImage.DEGREES_EACH_PIXEL), latLonBound.right, latLonBound.top,
				width / THUMBNAIL_DIVISION, height / THUMBNAIL_DIVISION + WATER_MARK_HEIGHT);
		try {
			thumbnailTries++;
			thumbnailTile = ImageIO.read(new URL(url));
			if (thumbnailTile == null)
				throw new Exception("Error downloading thumbnail image (" + thumbnailTries + "/" + MAX_TRIES + ") null thumbnailTile");
		} catch (MalformedURLException e) {
			throw new CriticalException(e.getMessage());
		} catch (Exception e) {
			System.out.println("Error downloading thumbnail image (" + thumbnailTries + "/" + MAX_TRIES + ")");
			if (thumbnailTries < MAX_TRIES) {
				try {
					Thread.sleep(SLEEP);
				} catch (InterruptedException e1) {
					throw new CriticalException(e1.getMessage());
				}
				return emptyTile();
			}
			else
				throw new CriticalException(e.getMessage());
		}
		int tWidth = thumbnailTile.getWidth();
		int tHeight = thumbnailTile.getHeight();
		for (int x = 0; x < tWidth; x++)
			for (int y = 0; y < tHeight; y++)
				if (thumbnailTile.getRGB(x, y) == Pixel.BUILDING)
					return false;
		return true;
	}
	
	public void load() throws CriticalException  {
		if (!loaded) {
			if (saved)
				loadFromDisk();
			else
				download();
		}
	}
	
	public void unload() {
		if (loaded) {
			tile = null;
			loaded = false;
			loadedTiles.remove(this);
			//System.out.println("unload(): "+ xTile+","+yTile+" savedTiles: "+savedTiles.size()+", loadedTiles: "+loadedTiles.size());
		}
	}

	public void saveToDisk() throws CriticalException {
		if ((!saved || Process.DEBUG) && !empty) {
			if (!loaded)
				download();
			File outputfile = new File(xTile + "_" + yTile + ".png");
	        try {
	            ImageIO.write(tile, "png", outputfile);
	            saved = true;
	            savedTiles.add(this);
	        } catch (IOException e) {
	        	throw new CriticalException(e.getMessage());
	        }
			//System.out.println("saveToDisk() "+ xTile+","+yTile+" savedTiles: "+savedTiles.size()+", loadedTiles: "+loadedTiles.size());
	        saved = true;
		}
	}

	public void loadFromDisk() throws CriticalException {
		File inputfile = new File(xTile + "_" + yTile + ".png");
		try {
			tile = ImageIO.read(inputfile);
			loaded = true;
			loadedTiles.add(this);
			//System.out.println("Load from disk: "+ xTile+","+yTile+" savedTiles: "+savedTiles.size()+", loadedTiles: "+loadedTiles.size());
		} catch (IOException e) {
			throw new CriticalException(e.getMessage());
		}
	}
	
	public void deleteFromDisk() {
		if (saved) {
			File inputfile = new File(xTile + "_" + yTile + ".png");
			inputfile.delete();
			saved = false;
			savedTiles.remove(this);
			//System.out.println("deleteFromDisk() "+ xTile+","+yTile+" savedTiles: "+savedTiles.size()+", loadedTiles: "+loadedTiles.size());
		}
	}
    
    public static void clean(int xTile, int yTile) throws CriticalException {
    	Tile t;
    	
    	// Free memory
    	int numLoaded = loadedTiles.size();
    	for (int i = 0; i < numLoaded; i++) {
    		t = loadedTiles.get(i);
    		// Keep bottom right tile and right tile into memory
    		if (t.getXtile() != (xTile + 1) || (t.getYtile() != yTile && t.getYtile() != (yTile + 1))) {
    			// Save to disk tiles we'll probably use after 
				if (t.getYtile() > yTile || (t.getYtile() == yTile && t.getXtile() > xTile))
					t.saveToDisk();
    			t.unload();
    			numLoaded--;
    			i--;
    		}
    	}
    	
    	if (!Process.DEBUG) { 
	    	// Delete useless tiles from disk
	    	int numSaved = savedTiles.size();
	    	for (int i = 0; i < numSaved; i++) {
	    		t = savedTiles.get(i);
	    		if ((t.getYtile() < yTile) || (t.getYtile() == yTile && t.getXtile() <= xTile)) {
	    			t.deleteFromDisk();
	    			numSaved--;
	    			i--;
	    		}
	    	}
    	}
    }
	
	private void closeTile() throws CriticalException {
		if (xTile == 0) {
			for(int y = 0; y < height; y++) {
				if (tile.getRGB(0, y) == Pixel.BUILDING)
					tile.setRGB(0, y, Pixel.BORDER);
			}	
		}
		if (xTile == (CadastreImage.getXtiles() - 1)) {
			int x = width - 1;
			for(int y = 0; y < height; y++) {
				if (tile.getRGB(x, y) == Pixel.BUILDING)
					tile.setRGB(x, y, Pixel.BORDER);
			}	
		}
		if (yTile == 0) {
			for(int x = 0; x < width; x++) {
				if (tile.getRGB(x, 0) == Pixel.BUILDING)
					tile.setRGB(x, 0, Pixel.BORDER);
			}	
		}
		if (yTile == (CadastreImage.getYtiles() - 1)) {
			int y = height - 1;
			for(int x = 0; x < width; x++) {
				if (tile.getRGB(x, y) == Pixel.BUILDING)
					tile.setRGB(x, y, Pixel.BORDER);
			}	
		}
	}
	
	public int getRGB(int x, int y) throws CriticalException {
		if (tile == null) {
			System.out.println("WARNING <------------- getRGB("+x+","+y+"), ("+xTile+","+yTile+") : tile is null");
			return Pixel.WHITE;
		}
		if (!loaded)
			download();
		if (empty)
			return Pixel.WHITE;
		return tile.getRGB(x, y);
	}
	
	public void setRGB(int x, int y, int rgb) throws CriticalException {
		if (tile == null) {
			System.out.println("WARNING <------------- setRGB("+x+","+y+"), ("+xTile+","+yTile+") : tile is null");
			return;
		}
		if (!loaded)
			download();
		if (empty)
    		System.out.println("WARNING: setRGB() to an empty tile");
		else
			tile.setRGB(x, y, rgb);
	}
	
	public Bound<Double> getLatLonBound() {
		return new Bound<Double>(latLonBound);
	}
	
	public Bound<Integer> getBound() {
		return new Bound<Integer>(bound);
	}
	
	public int getXtile() {
		return xTile;
	}
	
	public int getYtile() {
		return yTile;
	}
	
	public long getNumPixels() {
		return (long) width * (long) height;
	}
	
	public boolean isEmpty() {
		return empty;
	}
}
